using System;
using System.IO;
using PTP.Core;

namespace PTP.Devices
{
  /// <summary>
  /// Base class for PTP device. Holds the stream and handles session.
  /// </summary>
  public abstract class PTPDeviceBase : IPTPDevice
  {
    protected ContainerStream stream;

    public PTPDeviceBase(ContainerStream stream)
    {
      this.stream = stream;
    }

    bool sessionIsOpen = false;

    #region Transaction handling

    protected int currentTransactionID = 0;

    protected int getNewTransactionID()
    {
      return ++currentTransactionID;
    }

    #endregion

    public delegate void DataBlockHandler(DataBlockContainer container);


    protected virtual Container sendCommand(CommandBlockContainer command, bool setTransactionID, DataBlockHandler dataHandler)
    {
      return sendCommand(command, setTransactionID, dataHandler, null);
    }

    protected virtual Container sendCommand(CommandBlockContainer command, bool setTransactionID, DataBlockHandler dataHandler, DataBlockContainer dataContainer)
    {
      beginSendCommand(command, setTransactionID, dataContainer);

      return endSendCommand(command, dataHandler);
    }

    protected virtual void beginSendCommand(CommandBlockContainer command, bool setTransactionID, DataBlockContainer dataContainer)
    {

      if (setTransactionID)
      {
        if (!sessionIsOpen)
          this.OpenSession();
        command.Header.TransactionID = this.getNewTransactionID();
      }

      this.stream.WriteContainer(command);

      if (dataContainer != null)
      {
        dataContainer.Header.TransactionID = command.Header.TransactionID;
        this.stream.WriteContainer(dataContainer);
      }
    }

    protected virtual Container endSendCommand(CommandBlockContainer command, DataBlockHandler dataHandler)
    {
      Container response = this.stream.ReadContainer(command.Header.TransactionID, true);

      while (response.Header.ContainerType == ContainerType.DataBlock)
      {
        if (dataHandler != null)
          dataHandler((DataBlockContainer)response);
        response = this.stream.ReadContainer(command.Header.TransactionID, true);
      }

      return response;
    }


    public void OpenSession()
    {
      this.OpenSession(1);
    }



    #region IPTPDevice Members

    public DeviceInfo GetDeviceInfo()
    {
      DeviceInfo result = null;
      CommandBlockContainer command = new CommandBlockContainer((int)CommandCode.GetDeviceInfo);
      sendCommand(command, false, new DataBlockHandler(delegate(DataBlockContainer container)
      {
        if (result == null)
        {
          result = new DeviceInfo(container.Payload);
        }
      }));
      return result;
    }

    public void OpenSession(uint sessionID)
    {
      CommandBlockContainer command = new CommandBlockContainer((int)CommandCode.OpenSession, (int)sessionID);
      sendCommand(command, false, null);
    }

    public void CloseSession()
    {
      CommandBlockContainer command = new CommandBlockContainer((int)CommandCode.CloseSession);
      sendCommand(command, true, null);
      this.sessionIsOpen = false;
    }

    public uint[] GetStorageIDs()
    {
      uint[] result = null;
      CommandBlockContainer command = new CommandBlockContainer((int)CommandCode.GetStorageIDs);
      sendCommand(command, true, new DataBlockHandler(delegate(DataBlockContainer container)
      {
        if (result == null)
        {
          result = DataUtil.ReadUINT32ArrayToMemory(container.Payload);
        }
      }));
      return result;
    }

    public StorageInfo GetStorageInfo(uint storageID)
    {
      StorageInfo result = null;
      CommandBlockContainer command = new CommandBlockContainer((int)CommandCode.GetStorageInfo, (int)storageID);
      sendCommand(command, true, new DataBlockHandler(delegate(DataBlockContainer container)
      {
        if (result == null)
        {
          result = new StorageInfo(container.Payload);
        }
      }));
      return result;
    }

    #endregion
  }
}
